package cn.ipenta.blockchain.repo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import com.alibaba.fastjson.JSON;

import cn.ipenta.blockchain.BlockChainException;
import cn.ipenta.blockchain.entity.TransactionInfo;
import cn.ipenta.blockchain.utils.BeanUtils;
import cn.ipenta.jkb.entity.BaseEntity;
import cn.ipenta.jkb.repository.TransactionInfoRepository;

public abstract class AbstractBlockChainSource<T extends BaseEntity> {

	private static final Logger logger = LoggerFactory.getLogger(AbstractBlockChainSource.class);

	private String record_type = "undefault";

	private final String KEY_RECORDCONTENT = "contentInfo.recordContent";

	@Autowired
	BlockChainProvider blockChainProvider;

	@Autowired
	TransactionInfoRepository transactionInfoRepository;

	@Autowired
	MongoTemplate mongoTemplate;

	public AbstractBlockChainSource() {
		record_type = nameRecordType();
	}

	protected abstract String nameRecordType();

	public Boolean has(T t) throws BlockChainException {
		Query query = buildQuery(t);
		return mongoTemplate.exists(query, TransactionInfo.class);
	}

	public T create(T t) throws BlockChainException {
		String recordHash = blockChainProvider.createTx(record_type, reqFilter(t));
		if (recordHash != null) {
			return findTxListFromBlockChainAndSave(t);
		}
		return null;
	}
	
	public String createInBack(T t) throws BlockChainException {
		logger.debug("======{}",reqFilter(t));
		return blockChainProvider.createTx(record_type, reqFilter(t));
	}

	/**
	 * @param t
	 * @param referhash
	 */
	public T update(T t, String refertohash) throws BlockChainException {
		String recordHash = blockChainProvider.createTx(record_type, reqFilter(t), refertohash);
		if (recordHash != null) {
			return findTxListFromBlockChainAndSave(t);
		}
		return null;
	}

	public T find(T t) throws BlockChainException {
		return findInLocal(t);
	}
	
	public T findInLocal(T t) {
		Query query = buildQuery(t);

		List<TransactionInfo> transactionInfos = mongoTemplate.find(query, TransactionInfo.class);
		if (transactionInfos != null && !transactionInfos.isEmpty()) {
			return copyInfo(transactionInfos.get(0), t);
		}
		return null;

	}

	public T findTxListFromBlockChainAndSave(T t) throws BlockChainException {
		List<TransactionInfo> transactionInfos = findTxListFromBlockChain(t);
		logger.debug("88:{}",JSON.toJSON(transactionInfos));
		if (transactionInfos != null && !transactionInfos.isEmpty()) {
			for (TransactionInfo transactionInfo : transactionInfos) {
				logger.debug("93:{}",JSON.toJSON(transactionInfo));
				transactionInfoRepository.save(transactionInfo);
			}
			TransactionInfo info = transactionInfos.get(0);
			return copyInfo(info, t);
		}
		return null;
	}

	public List<TransactionInfo> findTxListFromBlockChain(T t) throws BlockChainException {
		return blockChainProvider.findTransactionInfoList(record_type, reqFilter(t));
	}

	
	/********************************辅助函数********************************/
	
	protected List<T> convertList(List<TransactionInfo> transactionInfos, T t) {
		
		List<T> list = new ArrayList<>();
 		if (transactionInfos != null && !transactionInfos.isEmpty()) {
			for (TransactionInfo transactionInfo : transactionInfos) {
				list.add(copyInfo(transactionInfo, t));
			}
		}
		return list;
	}
	
	
	@SuppressWarnings("unchecked")
	protected T copyInfo(TransactionInfo info, T t) throws BlockChainException{
		T entity = null;
		TreeMap<String, Object> map = info.getContentInfo().getRecordContent();
		try {
			entity = (T) t.getClass().newInstance();
			BeanUtils.mapToBean(map, entity);
			entity.setId(info.getUuid());
			return entity;
		} catch (InstantiationException | IllegalAccessException e) {
			throw new BlockChainException(e);
		}
	}
	
	/**
	 * @param t
	 * @return
	 */
	private Query buildQuery(T t) {
		Map<String, Object> map = reqFilterAsObject(t);

		Query query = new Query();

		for (Entry<String, Object> item : map.entrySet()) {
			query.addCriteria(Criteria.where(KEY_RECORDCONTENT + "." + item.getKey()).is(item.getValue()));
		}
		return query;
	}

	protected Map<String, String> reqFilter(T t) {
		Map<String, String> map = new HashMap<>();
		try {
			BeanUtils.mapConvert(t, map);
		} catch (Exception e) {
			logger.error("reqfilter:{}", e);
			throw new BlockChainException(e);
		}
		return map;
	}

	protected Map<String, Object> reqFilterAsObject(T t) {
		Map<String, Object> map = new HashMap<>();
		try {
			BeanUtils.mapConvertObject(t, map);
		} catch (Exception e) {
			logger.error("reqfilter:{}", e);
			throw new BlockChainException(e);
		}
		return map;
	}

}